{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "d006b2ea-9dfe-49c7-88a9-a5a0775185fd",
   "metadata": {},
   "source": [
    "# Additional End of week Exercise - week 2\n",
    "\n",
    "Now use everything you've learned from Week 2 to build a full prototype for the technical question/answerer you built in Week 1 Exercise.\n",
    "\n",
    "This should include a Gradio UI, streaming, use of the system prompt to add expertise, and the ability to switch between models. Bonus points if you can demonstrate use of a tool!\n",
    "\n",
    "If you feel bold, see if you can add audio input so you can talk to it, and have it respond with audio. ChatGPT or Claude can help you, or email me if you have questions.\n",
    "\n",
    "I will publish a full solution here soon - unless someone beats me to it...\n",
    "\n",
    "There are so many commercial applications for this, from a language tutor, to a company onboarding solution, to a companion AI to a course (like this one!) I can't wait to see your results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4c427d7c",
   "metadata": {},
   "outputs": [],
   "source": [
    "#imports\n",
    "import os\n",
    "import time\n",
    "import gradio as gr\n",
    "import openai\n",
    "from dotenv import load_dotenv\n",
    "import re\n",
    "\n",
    "load_dotenv(override=True)\n",
    "OPENAI_KEY = os.getenv(\"OPENAI_API_KEY\")\n",
    "GOOGLE_KEY = os.getenv(\"GOOGLE_API_KEY\")\n",
    "GEMINI_BASE_URL = os.getenv(\"GEMINI_BASE_URL\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "21e78ed3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# OpenAI / Gemini Client\n",
    "def get_client(model_choice):\n",
    "    \"\"\"\n",
    "    Return an OpenAI client configured for GPT or Gemini.\n",
    "    \"\"\"\n",
    "    if model_choice == \"OpenAI GPT-4\":\n",
    "        return openai.OpenAI(api_key=OPENAI_KEY)\n",
    "    else:\n",
    "        return openai.OpenAI(\n",
    "            api_key=GOOGLE_KEY,\n",
    "            base_url=GEMINI_BASE_URL,\n",
    "        )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8fb92ea9",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Fake Weather Tool\n",
    "def get_weather(location):\n",
    "    data = {\n",
    "        \"new york\": {\"temp\": 72, \"condition\": \"Partly Cloudy\"},\n",
    "        \"london\": {\"temp\": 59, \"condition\": \"Rainy\"},\n",
    "        \"tokyo\": {\"temp\": 68, \"condition\": \"Clear\"},\n",
    "    }\n",
    "    info = data.get(location.lower(), {\"temp\": 75, \"condition\": \"Sunny\"})\n",
    "    return f\"Weather in {location}: {info['temp']}°F, {info['condition']}\"\n",
    "\n",
    "\n",
    "def maybe_use_tool(message):\n",
    "    \"\"\"\n",
    "    Detect patterns like 'weather in <location>' (case-insensitive)\n",
    "    and inject tool result.\n",
    "    Supports multi-word locations, e.g. \"New York\" or \"tokyo\".\n",
    "    \"\"\"\n",
    "    pattern = re.compile(r\"weather\\s+in\\s+([A-Za-z\\s]+)\", re.IGNORECASE)\n",
    "    match = pattern.search(message)\n",
    "\n",
    "    if match:\n",
    "        location = match.group(1).strip(\" ?.,!\").title()\n",
    "        tool_result = get_weather(location)\n",
    "        return f\"{message}\\n\\n[Tool used: {tool_result}]\"\n",
    "\n",
    "    return message"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "672621a6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# prompt\n",
    "SYSTEM_PROMPTS = {\n",
    "    \"General Assistant\": \"You are a helpful and polite AI assistant.\",\n",
    "    \"Technical Expert\": \"You are an expert software engineer who writes clear, correct code.\",\n",
    "    \"Creative Writer\": \"You are a creative storyteller who writes imaginative and emotional prose.\",\n",
    "    \"Science Tutor\": \"You are a science teacher who explains ideas simply and clearly.\",\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "21525edd",
   "metadata": {},
   "outputs": [],
   "source": [
    "# ---------------------------------------------\n",
    "# Build chat messages\n",
    "# ---------------------------------------------\n",
    "def build_messages(history, user_msg, persona):\n",
    "    messages = [{\"role\": \"system\", \"content\": SYSTEM_PROMPTS[persona]}]\n",
    "    for u, a in history:\n",
    "        messages.append({\"role\": \"user\", \"content\": u})\n",
    "        messages.append({\"role\": \"assistant\", \"content\": a})\n",
    "    messages.append({\"role\": \"user\", \"content\": user_msg})\n",
    "    return messages\n",
    "\n",
    "\n",
    "# ---------------------------------------------\n",
    "# Stream model output\n",
    "# ---------------------------------------------\n",
    "def stream_response(model_choice, messages):\n",
    "    \"\"\"\n",
    "    Uses the same openai library to stream from GPT or Gemini.\n",
    "    \"\"\"\n",
    "    client = get_client(model_choice)\n",
    "    model = \"gpt-4o-mini\" if model_choice == \"OpenAI GPT-4\" else \"gemini-2.5-flash\"\n",
    "\n",
    "    stream = client.chat.completions.create(\n",
    "        model=model,\n",
    "        messages=messages,\n",
    "        stream=True,\n",
    "    )\n",
    "\n",
    "    reply = \"\"\n",
    "    for chunk in stream:\n",
    "        if chunk.choices[0].delta and chunk.choices[0].delta.content:\n",
    "            reply += chunk.choices[0].delta.content\n",
    "            yield reply\n",
    "        time.sleep(0.01)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c88976b1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Gradio UI\n",
    "with gr.Blocks(theme=gr.themes.Soft()) as demo:\n",
    "    gr.Markdown(\n",
    "    \"\"\"\n",
    "    # 🤖 Unified GPT + Gemini Chat\n",
    "\n",
    "    - 🔀 Choose model: **OpenAI GPT-4** or **Gemini 2.5 Flash**\n",
    "    - 🧠 Pick the assistant persona (system prompt injection)\n",
    "    - 🛠 Tool support: ask about weather\n",
    "\n",
    "    **Weather tool tips:**\n",
    "    - Ask: \"What's the weather in London?\"\n",
    "    - Also works for: New York, Tokyo\n",
    "    - If a city isn't known, it returns a default sunny forecast\n",
    "    \"\"\"\n",
    "    )\n",
    "\n",
    "    with gr.Row():\n",
    "        model_choice = gr.Dropdown(\n",
    "            [\"OpenAI GPT-4\", \"Gemini 2.5 Flash\"],\n",
    "            value=\"OpenAI GPT-4\",\n",
    "            label=\"Model\",\n",
    "        )\n",
    "        persona = gr.Dropdown(\n",
    "            list(SYSTEM_PROMPTS.keys()),\n",
    "            value=\"General Assistant\",\n",
    "            label=\"Persona\",\n",
    "        )\n",
    "\n",
    "    chatbot = gr.Chatbot(height=400)\n",
    "    msg = gr.Textbox(placeholder=\"Ask about weather or coding...\", label=\"Your message\")\n",
    "    gr.Markdown(\n",
    "        \"💡 Tip: You can ask about the weather in **London**, **New York**, or **Tokyo**. \"\n",
    "        \"I'll call a local tool and include that info in my answer.\"\n",
    "    )\n",
    "    send = gr.Button(\"Send\", variant=\"primary\")\n",
    "    clear = gr.Button(\"Clear\")\n",
    "\n",
    "    state = gr.State([])\n",
    "\n",
    "    msg.submit(chat_fn, [msg, state, model_choice, persona], chatbot).then(\n",
    "        lambda chat: chat, chatbot, state\n",
    "    ).then(lambda: \"\", None, msg)\n",
    "\n",
    "    send.click(chat_fn, [msg, state, model_choice, persona], chatbot).then(\n",
    "        lambda chat: chat, chatbot, state\n",
    "    ).then(lambda: \"\", None, msg)\n",
    "\n",
    "    clear.click(lambda: ([], []), None, [chatbot, state], queue=False)\n",
    "\n",
    "if __name__ == \"__main__\":\n",
    "    demo.launch()\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "llm-engineering (3.12.10)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
